package com.derbysoft.synxisadapter.commons.repository.impl;

import com.derbysoft.core.CriteriaCreator;
import com.derbysoft.core.Limit;
import com.derbysoft.core.LimitedList;
import com.derbysoft.core.Sorter;
import com.derbysoft.core.utils.DateTimeUtils;
import com.derbysoft.synxisadapter.commons.exception.ERSPDuplicatedException;
import com.derbysoft.synxisadapter.commons.log.ReservationPair;
import com.derbysoft.synxisadapter.commons.model.Reservation;
import com.derbysoft.synxisadapter.commons.model.ReservationItem;
import com.derbysoft.synxisadapter.commons.model.ReservationLog;
import com.derbysoft.synxisadapter.commons.repository.ReservationRepository;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class ReservationRepositoryImpl extends BaseRepositorySupport implements ReservationRepository {

    @Override
    public Reservation findByErspCode(final String ersp) {
        return dataAccessor.findUnique(Reservation.class, new CriteriaCreator() {
            @Override
            public Criteria createCriteria(Session session) {
                Criteria criteria = session.createCriteria(Reservation.class);
                criteria.add(Restrictions.eq("ersp", ersp));
                return criteria;
            }
        });
    }

    @Override
    public List<ReservationItem> loadReservationItems(final Reservation reservation) {
        CriteriaCreator criteriaCreator = new CriteriaCreator() {
            @Override
            public Criteria createCriteria(Session session) {
                Criteria criteria = session.createCriteria(ReservationItem.class);
                criteria.add(Restrictions.eq("reservation", reservation));
                return criteria;
            }
        };
        return dataAccessor.findByCriteria(ReservationItem.class, criteriaCreator, null, null);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public Reservation create(String ersp, String hotelId, String passport) {
        if (findByErspCode(ersp) != null) {
            throw new ERSPDuplicatedException(ersp);
        }
        Reservation reservation = new Reservation();
        reservation.setErsp(ersp);
        reservation.setHotelId(hotelId);
        reservation.setHotelPassport(passport);
        reservation.setLastUpdateTime(new Date());

        dataAccessor.save(reservation);
        return reservation;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void cancel(String ersp, String confirmNumber, String cancelNumber) {
        Reservation reservation = findByErspCode(ersp);
        for (ReservationItem reservationItem : reservation.getReservationItems()) {
            if (reservationItem.getConfirmNumber().equals(confirmNumber)) {
                reservationItem.setCancelNumber(cancelNumber);
                reservationItem.setCancelTime(new Date());
            }
        }
        dataAccessor.update(reservation);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void addReservationItem(String ersp, String roomTypeCode, String ratePlaneCode, String confirmNumber) {
        Reservation reservation = findByErspCode(ersp);
        reservation.getReservationItems().add(
                createReservationItem(reservation, roomTypeCode, ratePlaneCode, confirmNumber));
        reservation.setLastUpdateTime(new Date());
        dataAccessor.update(reservation);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void updateStatus(String ersp, Reservation.Status status) {
        Reservation reservation = findByErspCode(ersp);
        reservation.setStatus(status);
        reservation.setLastUpdateTime(new Date());
        dataAccessor.update(reservation);
    }

    @Override
    public LimitedList<Reservation> findByCondition(final ReservationCondition condition, Limit limit) {
        List<Object> params = new ArrayList<Object>();
        String hql = populateHql(condition, params);
        return dataAccessor.findByHql(
                Reservation.class, hql, params.toArray(), limit, new Sorter().desc("createTime"));
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void appendLog(String erspCode, String hotelPassport, ReservationLog reservationLog, Date timestamp) {
        Reservation reservation = findByErspCode(erspCode);
        if (reservation == null) {
            return;
        }
        reservation.setLastUpdateTime(timestamp);
        reservationLog.setReservation(reservation);
        reservation.appendLog(reservationLog);

        dataAccessor.update(reservation);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void update(ReservationPair reservationPair) {
        Reservation reservation = findByErspCode(reservationPair.getErsp());
        if (reservation == null) {
            return;
        }
        reservation.setLastUpdateTime(new Date());
        reservationPair.getReservationLog().setReservation(reservation);
        reservation.appendLog(reservationPair.getReservationLog());
        dataAccessor.update(reservation);
    }

    private String populateHql(ReservationCondition condition, List<Object> params) {
        StringBuilder sb = new StringBuilder();
        sb.append("FROM Reservation WHERE 1=1");

        if (StringUtils.isNotEmpty(condition.getHotelId())) {
            sb.append(" AND hotelId=?");
            params.add(condition.getHotelId());
        }

        if (condition.getStatus() != null) {
            sb.append(" AND status=?");
            params.add(condition.getStatus());
        }

        if (condition.getAfter() != null) {
            sb.append(" AND createTime>=?");
            params.add(condition.getAfter());
        }

        if (condition.getBefore() != null) {
            sb.append(" AND createTime<?");
            params.add(DateTimeUtils.offsetDays(condition.getBefore(), 1));
        }

        if (condition.getErsp() != null) {
            sb.append(" AND ersp LIKE ?");
            params.add(String.format("%%%s%%", condition.getErsp()));
        }

        if (condition.getResNumber() != null) {
            sb.append(" AND id IN (" +
                    "SELECT reservation FROM ReservationItem WHERE confirmNumber LIKE ?)");
            params.add(String.format("%%%s%%", condition.getResNumber()));
        }

        return sb.toString();
    }

    private ReservationItem createReservationItem(Reservation reservation,
                                                  String roomTypeCode,
                                                  String ratePlaneCode,
                                                  String confirmNumber) {
        ReservationItem reservationItem = new ReservationItem();

        reservationItem.setReservation(reservation);
        reservationItem.setRoomTypeCode(roomTypeCode);
        reservationItem.setRatePlanCode(ratePlaneCode);
        reservationItem.setConfirmNumber(confirmNumber);

        return reservationItem;
    }
}